<?php
namespace Lib;

/**
 * 数据分页，可被子类继承，也可直接被实例化
 * 
 * @author SUSISOY
 * 
 */
class Pager {
	/**
	 * 设置每页条数
	 * @var int
	 */
	const SET_PAGE_SIZE = 1;
	/**
	 * 设置每组的页数
	 * @var int
	 */
	const SET_GROUP_SIZE = 2;
	/**
	 * 设置下一次获取的页号
	 * @var int
	 */
	const SET_FETCH_PAGE = 3;
	
	/**
	 * 数据的总条数
	 * @var int
	 * @access public
	 */
	public $count = 0;
	/**
	 * 最近一次所获取数据的最后一条在总条数中的偏移量
	 * - 第一条的偏移量为 1
	 * - 最近一次获取失败或获取空，则偏移量为 0
	 * @var int
	 * @access public
	 */
	public $offset = 0;
	/**
	 * 最近一次获取或预设的页号
	 * @var int
	 * @access public
	 */
	public $current = 1;
	
	/**
	 * 每页的条数
	 * @var int
	 * @access protected
	 */
	protected $size = 25;
	/**
	 * 每组的页数，设置为 1 时适用于仅有“上一页”和“下一页”的应用
	 * @var int
	 * @access protected
	 */
	protected $gsize = 10;
	/**
	 * 包含有“LIMIT”的SQL语句查询结果
	 * @var array
	 * @access protected
	 */
	protected $data = null;
	/**
	 * 数据库操作句柄
	 * @var object
	 * @access protected
	 */
	protected $handle = null;
	/**
	 * 数据库查询SQL语句
	 * @var string
	 * @access protected
	 */
	protected $sql = null;
	
	/**
	 * 初始化一个数据分页对象，并允许设置该对象的数据库操作句柄，注意：初始化动作中并不包含对所设置的数据库操作句柄进行有效性检验
	 * 
	 * @param object $handle
	 * @return void
	 */
	public function __construct($handle = null) {
		$this->handle = $handle;
	}
	
	/**
	 * 设置当前数据分页对象的数据库操作句柄
	 * 
	 * @param object $handle
	 * @return boolean
	 */
	public function handle($handle) {
		if (!is_object($handle)) {
			return false;
		}
		
		$this->handle = $handle;
		
		return true;
	}
	
	/**
	 * 初始化一个数据库查询，之后即可获得分页信息，并使用 self::fetch() 获取指定页的数据
	 * 
	 * @param string $sql
	 * @return boolean
	 */
	public function select($sql) {
		$this->sql = $sql;
		
		$this->data = array();
		
		if (preg_match('/LIMIT\s+(?:\d+\s*,|OFFSET\s*)?\d+\s*$/i', $this->sql)) {
			$result = $this->handle->execute($this->sql);
			if (!$result) {
				return false;
			}
			
			$this->data = $result;
			
			$this->count = $result->count;
		} else {
			$result = $this->handle->execute(preg_replace('/^SELECT.+FROM(.+)$/i', 'SELECT COUNT(*) AS rows FROM $1', preg_replace("/^(.+)LIMIT.+$/i", '$1', $this->sql)));
			if (!$result) {
				return false;
			}
			
			$this->count = (int) $result[0]['rows'];
		}
		
		return true;
	}
	
	/**
	 * 设置外部数据源
	 * 
	 * @param data array
	 * @return boolean
	 */
	public function source($data) {
		if (!is_array($data)) {
			return false;
		}
		
		$this->data = $data;
		
		$this->count = count($this->data);
		
		return true;
	}
	
	/**
	 * 设置分页参数
	 * 
	 * @param int $option
	 * @param mixed $value
	 * @return boolean
	 */
	public function set($option, $value) {
		switch ($option) {
			case self::SET_PAGE_SIZE :
				if ($value < 1) {
					break;
				}
				$this->size = $value;
				
				return true;
			case self::SET_GROUP_SIZE :
				if ($value < 1) {
					break;
				}
				$this->gsize = $value;
				
				return true;
			case self::SET_FETCH_PAGE :
				$this->current = $value < 1 ? 1 : $value;
				
				return true;
		}
		
		return false;
	}
	
	/**
	 * 获得分页参数
	 * 
	 * @param int $option
	 * @return mixed
	 */
	public function get($option) {
		switch ($option) {
			case self::SET_PAGE_SIZE :
				return $this->size;
			case self::SET_GROUP_SIZE :
				return $this->gsize;
		}
	}
	
	/**
	 * 获得指定页号的数据列表，成功则返回非空数组，失败则返回空数组。
	 * 
	 * 第一页的页号为“1”。
	 * 
	 * @param int $page
	 * @param int $size
	 * @param int $gsize
	 * @return array
	 */
	public function fetch($page = null, $size = null, $gsize = null) {
		$page && $this->set(self::SET_FETCH_PAGE, $page);
		$size && $this->set(self::SET_PAGE_SIZE, $size);
		$gsize && $this->set(self::SET_GROUP_SIZE, $gsize);
		
		$start = $this->current * $this->size - $this->size;
		
		$this->offset = 0;
		
		if ($start >= $this->count) {
			return array();
		}
		
		$data = array();
		
		if ($this->data) {
			$data = array_slice($this->data, $start, $this->size);
			
			$this->offset = $start + count($data);
		} elseif ($this->count > 0) {
			$count = $start + $this->size > $this->count ? $this->count - $start : $this->size;
			
			$this->offset = $start + $count;
			
			$data = $this->handle->execute($this->sql .= " LIMIT $start,$count")->fetch();
		}
		
		return $data;
	}
	
	/**
	 * 获得总页数
	 * 
	 * @return int
	 */
	public function total() {
		return floor($this->count / $this->size) + ($this->count % $this->size ? 1 : 0);
	}
	
	/**
	 * 获得当前组中的页号列表，成功则返回非空数组，失败则返回空数组
	 * 
	 * @return array
	 */
	public function group() {
		$group = array();
		
		$total = $this->total();
		
		if ($this->current <= $total) {
			$home = floor(($this->current - 1) / $this->gsize) * $this->gsize + 1;
			
			for ($i = 0; $i < $this->gsize && $home + $i <= $total; $i++) {
				$group[] = $home + $i;
			}
		}
		
		return $group;
	}
	
	/**
	 * 获得上一组的首页页号，若没有上一组，则返回0
	 * 
	 * @return int
	 */
	public function back() {
		$home = floor(($this->current - 1) / $this->gsize) * $this->gsize + 1 - $this->gsize;
		
		return $home > 0 ? $home : 0;
	}
	
	/**
	 * 获得下一组的首页页号，若没有下一组，则返回0
	 * 
	 * @return int
	 */
	public function next() {
		$home = floor(($this->current - 1) / $this->gsize) * $this->gsize + 1 + $this->gsize;
		
		return $home > $this->total() ? 0 : $home;
	}
	
	/**
	 * 获得最后一组的首页页号
	 * 
	 * @return int
	 */
	public function last() {
		return floor(($this->total() - 1) / $this->gsize) * $this->gsize + 1;
	}
}